Completed
Pull Request — master (#99)
by Ruben de
55s
created

APIClient.getWebhookEvents   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
nc 2
dl 0
loc 10
rs 9.4285
nop 3
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
    /**
122
     * @type RestClient
123
     */
124
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
125
126
    if (options.btccom) {
127
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
128
    } else {
129
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
130
    }
131
132
};
133
134
APIClient.normalizeNetworkFromOptions = function(options) {
135
    /* jshint -W071, -W074 */
136
    var network = 'BTC';
137
    var testnet = false;
138
    var regtest = false;
139
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
140
141
    var prefix;
142
    var done = false;
143
144
    if (options.network) {
145
        var lower = options.network.toLowerCase();
146
147
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
148
        if (!m) {
149
            throw new Error("Invalid network [" + options.network + "]");
150
        }
151
152
        if (m[2] === 'btc') {
153
            network = "BTC";
154
        } else {
155
            network = "BCC";
156
        }
157
158
        prefix = m[1];
159
        if (prefix) {
160
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
161
            done = true;
162
            if (prefix === 'r') {
163
                testnet = true;
164
                regtest = true;
165
            } else if (prefix === 't') {
166
                testnet = true;
167
            }
168
        }
169
    }
170
171
    // if we're not already done then apply options.regtest and options.testnet
172
    if (!done) {
173
        if (options.regtest) {
174
            testnet = true;
175
            regtest = true;
176
            prefix = "r";
177
        } else if (options.testnet) {
178
            testnet = true;
179
            prefix = "t";
180
        }
181
    }
182
183
    apiNetwork = (prefix || "") + network;
184
185
    return [network, testnet, regtest, apiNetwork];
186
};
187
188
APIClient.initRestClient = function(options) {
189
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
190
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
191
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
192
    }
193
194
    // trim off leading https?://
195
    if (options.host && options.host.indexOf("https://") === 0) {
196
        options.https = true;
197
        options.host = options.host.substr(8);
198
    } else if (options.host && options.host.indexOf("http://") === 0) {
199
        options.https = false;
200
        options.host = options.host.substr(7);
201
    }
202
203
    if (typeof options.https === "undefined") {
204
        options.https = true;
205
    }
206
207
    if (!options.port) {
208
        options.port = options.https ? 443 : 80;
209
    }
210
211
    if (options.btccom) {
212
        if (!options.host) {
213
            options.host = (options.testnet && 't') + 'chain.api.btc.com';
214
        }
215
216
        if (!options.endpoint) {
217
            options.endpoint = "/" + (options.apiVersion || "v3");
218
        }
219
220
    } else {
221
        if (!options.host) {
222
            options.host = 'api.blocktrail.com';
223
        }
224
225
        if (!options.endpoint) {
226
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
227
        }
228
    }
229
230
    return new RestClient(options);
231
};
232
233
var determineDataStorageV2_3 = function(options) {
234
    return q.when(options)
235
        .then(function(options) {
236
            // legacy
237
            if (options.storePrimaryMnemonic) {
238
                options.storeDataOnServer = options.storePrimaryMnemonic;
239
            }
240
241
            // storeDataOnServer=false when primarySeed is provided
242
            if (typeof options.storeDataOnServer === "undefined") {
243
                options.storeDataOnServer = !options.primarySeed;
244
            }
245
246
            return options;
247
        });
248
};
249
250
var produceEncryptedDataV2 = function(options, notify) {
251
    return q.when(options)
252
        .then(function(options) {
253
            if (options.storeDataOnServer) {
254
                if (!options.secret) {
255
                    if (!options.passphrase) {
256
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
257
                    }
258
259
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
260
261
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
262
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
263
                }
264
265
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
266
267
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
268
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
269
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
270
271
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
272
273
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
274
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
275
            }
276
277
            return options;
278
        });
279
};
280
281
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
282
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
283
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
284
        var saltBuf = Encryption.generateSalt();
285
        var iv = Encryption.generateIV();
286
287
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
288
            return require('./webworker');
289
        }, onLoadWorkerLoadAsmCrypto, {
290
            method: 'Encryption.encryptWithSaltAndIV',
291
            pt: pt,
292
            pw: pw,
293
            saltBuf: saltBuf,
294
            iv: iv,
295
            iterations: iter
296
        })
297
            .then(function(data) {
298
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
299
            });
300
    } else {
301
        try {
302
            return q.when(Encryption.encrypt(pt, pw, iter));
303
        } catch (e) {
304
            return q.reject(e);
305
        }
306
    }
307
};
308
309
APIClient.prototype.promisedDecrypt = function(ct, pw) {
310
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
311
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
312
            return require('./webworker');
313
        }, onLoadWorkerLoadAsmCrypto, {
314
            method: 'Encryption.decrypt',
315
            ct: ct,
316
            pw: pw
317
        })
318
            .then(function(data) {
319
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
320
            });
321
    } else {
322
        try {
323
            return q.when(Encryption.decrypt(ct, pw));
324
        } catch (e) {
325
            return q.reject(e);
326
        }
327
    }
328
};
329
330
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
331
    var self = this;
332
333
    return q.when(options)
334
        .then(function(options) {
335
            if (options.storeDataOnServer) {
336
                return q.when()
337
                    .then(function() {
338
                        if (!options.secret) {
339
                            if (!options.passphrase) {
340
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
341
                            }
342
343
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
344
345
                            // -> now a buffer
346
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
347
348
                            // -> now a buffer
349
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
350
                                .then(function(encryptedSecret) {
351
                                    options.encryptedSecret = encryptedSecret;
352
                                });
353
                        } else {
354
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
355
                                throw new Error('Secret must be a buffer');
356
                            }
357
                        }
358
                    })
359
                    .then(function() {
360
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
361
362
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
363
                            .then(function(encryptedPrimarySeed) {
364
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
365
                            });
366
                    })
367
                    .then(function() {
368
                        // skip generating recovery secret when explicitly set to false
369
                        if (options.recoverySecret === false) {
370
                            return;
371
                        }
372
373
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
374
                        if (!options.recoverySecret) {
375
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
376
                        }
377
378
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
379
                            .then(function(recoveryEncryptedSecret) {
380
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
381
                            });
382
                    })
383
                    .then(function() {
384
                        return options;
385
                    });
386
            } else {
387
                return options;
388
            }
389
        });
390
};
391
392
var doRemainingWalletDataV2_3 = function(options, network, notify) {
393
    return q.when(options)
394
        .then(function(options) {
395
            if (!options.backupPublicKey) {
396
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
397
            }
398
399
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
400
401
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
402
403
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
404
405
            if (!options.backupPublicKey) {
406
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
407
                options.backupPublicKey = options.backupPrivateKey.neutered();
408
            }
409
410
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
411
412
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
413
414
            return options;
415
        });
416
};
417
418
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
419
    var self = this;
420
421
    var deferred = q.defer();
422
    deferred.promise.spreadNodeify(cb);
423
424
    deferred.resolve(q.fcall(function() {
425
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
426
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
427
        });
428
    }));
429
430
    return deferred.promise;
431
};
432
433
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
434
    var self = this;
435
436
    if (useWebWorker) {
437
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
438
            return require('./webworker');
439
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
440
            .then(function(data) {
441
                return data.seed;
442
            });
443
    } else {
444
        try {
445
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
446
        } catch (e) {
447
            return q.reject(e);
448
        }
449
    }
450
};
451
452
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
453
    var self = this;
454
455
    var deferred = q.defer();
456
    deferred.promise.nodeify(cb);
457
458
    try {
459
        // avoid conflicting options
460
        if (options.passphrase && options.password) {
461
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
462
        }
463
        // normalize passphrase/password
464
        options.passphrase = options.passphrase || options.password;
465
        delete options.password;
466
467
        // avoid conflicting options
468
        if (options.primaryMnemonic && options.primarySeed) {
469
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
470
        }
471
472
        // avoid deprecated options
473
        if (options.primaryPrivateKey) {
474
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
475
        }
476
477
        // make sure we have at least one thing to use
478
        if (!options.primaryMnemonic && !options.primarySeed) {
479
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
480
        }
481
482
        if (options.primarySeed) {
483
            self.primarySeed = options.primarySeed;
484
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
485
            deferred.resolve(options);
486
        } else {
487
            if (!options.passphrase) {
488
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
489
            }
490
491
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
492
                .then(function(seedHex) {
493
                    try {
494
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
495
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
496
                        deferred.resolve(options);
497
                    } catch (e) {
498
                        deferred.reject(e);
499
                    }
500
                }, function(e) {
501
                    deferred.reject(e);
502
                });
503
        }
504
    } catch (e) {
505
        deferred.reject(e);
506
    }
507
508
    return deferred.promise;
509
};
510
511
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
512
    var self = this;
513
514
    var deferred = q.defer();
515
    deferred.promise.nodeify(cb);
516
517
    try {
518
        // avoid conflicting options
519
        if (options.backupMnemonic && options.backupPublicKey) {
520
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
521
        }
522
523
        // make sure we have at least one thing to use
524
        if (!options.backupMnemonic && !options.backupPublicKey) {
525
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
526
        }
527
528
        if (options.backupPublicKey) {
529
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
530
                deferred.resolve(options);
531
            } else {
532
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
533
                deferred.resolve(options);
534
            }
535
        } else {
536
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
537
                options.backupPublicKey = backupPrivateKey.neutered();
538
                deferred.resolve(options);
539
            }, function(e) {
540
                deferred.reject(e);
541
            });
542
        }
543
    } catch (e) {
544
        deferred.reject(e);
545
    }
546
547
    return deferred.promise;
548
};
549
550
APIClient.prototype.debugAuth = function(cb) {
551
    var self = this;
552
553
    return self.dataClient.get("/debug/http-signature", null, true, cb);
554
};
555
556
/**
557
 * get a single address
558
 *
559
 * @param address      string       address hash
560
 * @param [cb]          function    callback function to call when request is complete
561
 * @return q.Promise
562
 */
563
APIClient.prototype.address = function(address, cb) {
564
    var self = this;
565
566
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
567
        .then(function(data) {
568
            return self.converter.handleErros(self, data);
569
        })
570
        .then(function(data) {
571
            if (data === null) {
572
                return data;
573
            } else {
574
                return self.converter.convertAddress(data);
575
            }
576
        }), cb);
577
};
578
579
APIClient.prototype.addresses = function(addresses, cb) {
580
    var self = this;
581
582
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
583
};
584
585
586
/**
587
 * get all transactions for an address (paginated)
588
 *
589
 * @param address       string      address hash
590
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
591
 * @param [cb]          function    callback function to call when request is complete
592
 * @return q.Promise
593
 */
594
APIClient.prototype.addressTransactions = function(address, params, cb) {
595
596
    var self = this;
597
598
    if (typeof params === "function") {
599
        cb = params;
600
        params = null;
601
    }
602
603
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
604
        .then(function(data) {
605
            return self.converter.handleErros(self, data);
606
        })
607
        .then(function(data) {
608
            return data.data === null ? data : self.converter.convertAddressTxs(data);
609
        }), cb);
610
};
611
612
/**
613
 * get all transactions for a batch of addresses (paginated)
614
 *
615
 * @param addresses     array       address hashes
616
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
617
 * @param [cb]          function    callback function to call when request is complete
618
 * @return q.Promise
619
 */
620
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
621
    var self = this;
622
623
    if (typeof params === "function") {
624
        cb = params;
625
        params = null;
626
    }
627
628
    return self.dataClient.post("/address/has-transactions", self.converter.paginationParams(params), {"addresses": addresses}, cb);
629
};
630
631
/**
632
 * get all unconfirmed transactions for an address (paginated)
633
 *
634
 * @param address       string      address hash
635
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
636
 * @param [cb]          function    callback function to call when request is complete
637
 * @return q.Promise
638
 */
639
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
640
    var self = this;
641
642
    if (typeof params === "function") {
643
        cb = params;
644
        params = null;
645
    }
646
647
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
648
        .then(function(data) {
649
            return self.converter.handleErros(self, data);
650
        })
651
        .then(function(data) {
652
            if (data.data === null) {
653
                return data;
654
            }
655
656
            var res = self.converter.convertAddressTxs(data);
657
            res.data = res.data.filter(function(tx) {
658
                return !tx.confirmations;
659
            });
660
661
            return res;
662
        }), cb);
663
};
664
665
/**
666
 * get all unspent outputs for an address (paginated)
667
 *
668
 * @param address       string      address hash
669
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
670
 * @param [cb]          function    callback function to call when request is complete
671
 * @return q.Promise
672
 */
673
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
674
    var self = this;
675
676
    if (typeof params === "function") {
677
        cb = params;
678
        params = null;
679
    }
680
681
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
682
        .then(function(data) {
683
            return self.converter.handleErros(self, data);
684
        })
685
        .then(function(data) {
686
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
687
        }), cb);
688
};
689
690
/**
691
 * get all unspent outputs for a batch of addresses (paginated)
692
 *
693
 * @param addresses     array       address hashes
694
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
695
 * @param [cb]          function    callback function to call when request is complete
696
 * @return q.Promise
697
 */
698
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
699
    var self = this;
700
701
    if (self.converter instanceof BtccomConverter) {
702
        throw new Error("Not implemented");
703
    }
704
705
    if (typeof params === "function") {
706
        cb = params;
707
        params = null;
708
    }
709
710
    return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
711
};
712
713
/**
714
 * verify ownership of an address
715
 *
716
 * @param address       string      address hash
717
 * @param signature     string      a signed message (the address hash) using the private key of the address
718
 * @param [cb]          function    callback function to call when request is complete
719
 * @return q.Promise
720
 */
721
APIClient.prototype.verifyAddress = function(address, signature, cb) {
722
    var self = this;
723
724
    return self.verifyMessage(address, address, signature, cb);
725
};
726
727
/**
728
 *
729
 * get all blocks (paginated)
730
 * ASK
731
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
732
 * @param [cb]          function    callback function to call when request is complete
733
 * @return q.Promise
734
 */
735
APIClient.prototype.allBlocks = function(params, cb) {
736
    var self = this;
737
738
    if (self.converter instanceof BtccomConverter) {
739
        throw new Error("Not implemented");
740
    }
741
742
    if (typeof params === "function") {
743
        cb = params;
744
        params = null;
745
    }
746
747
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)), cb);
748
};
749
750
/**
751
 * get a block
752
 *
753
 * @param block         string|int  a block hash or a block height
754
 * @param [cb]          function    callback function to call when request is complete
755
 * @return q.Promise
756
 */
757
APIClient.prototype.block = function(block, cb) {
758
    var self = this;
759
760
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
761
        .then(function(data) {
762
            return self.converter.handleErros(self, data);
763
        })
764
        .then(function(data) {
765
            return data.data === null ? data : self.converter.convertBlock(data);
766
        }), cb);
767
};
768
769
/**
770
 * get the latest block
771
 *
772
 * @param [cb]          function    callback function to call when request is complete
773
 * @return q.Promise
774
 */
775
APIClient.prototype.blockLatest = function(cb) {
776
    var self = this;
777
778
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
779
        .then(function(data) {
780
            return self.converter.handleErros(self, data);
781
        })
782
        .then(function(data) {
783
            return data.data === null ? data : self.converter.convertBlock(data);
784
        }), cb);
785
};
786
787
/**
788
 * get all transactions for a block (paginated)
789
 *
790
 * @param block         string|int  a block hash or a block height
791
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
792
 * @param [cb]          function    callback function to call when request is complete
793
 * @return q.Promise
794
 */
795
APIClient.prototype.blockTransactions = function(block, params, cb) {
796
    var self = this;
797
798
    if (typeof params === "function") {
799
        cb = params;
800
        params = null;
801
    }
802
803
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
804
        .then(function(data) {
805
            return self.converter.handleErros(self, data);
806
        })
807
        .then(function(data) {
808
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
809
        }), cb);
810
};
811
812
/**
813
 * get a single transaction
814
 *
815
 * @param tx            string      transaction hash
816
 * @param [cb]          function    callback function to call when request is complete
817
 * @return q.Promise
818
 */
819
APIClient.prototype.transaction = function(tx, cb) {
820
    var self = this;
821
822
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
823
        .then(function(data) {
824
            return self.converter.handleErros(self, data);
825
        })
826
        .then(function(data) {
827
            if (data.data === null) {
828
                return data;
829
            } else {
830
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
831
                if (self.converter instanceof BtccomConverter) {
832
                    var txPath = data.data.hash + ".rawhex";
833
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
834
                        .then(function(rawTx) {
835
                            return [data, rawTx];
836
                        })
837
                        .then(function(dataAndTx) {
838
                            if (dataAndTx !== null) {
839
                                var data = dataAndTx[0];
840
                                var rawTx = dataAndTx[1];
841
                                return self.converter.convertTx(data, rawTx);
842
                            } else {
843
                                return dataAndTx;
844
                            }
845
                        });
846
                } else {
847
                    return self.converter.convertTx(data);
848
                }
849
            }
850
        }), cb);
851
};
852
853
/**
854
 * get a batch of transactions
855
 *
856
 * @param txs           string[]    list of transaction hashes (txId)
857
 * @param [cb]          function    callback function to call when request is complete
858
 * @return q.Promise
859
 */
860
APIClient.prototype.transactions = function(txs, cb) {
861
    var self = this;
862
863
    if (self.converter instanceof BtccomConverter) {
864
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
865
            .then(function(data) {
866
                return self.converter.handleErros(self, data);
867
            })
868
            .then(function(data) {
869
                if (data.data === null) {
870
                    return data;
871
                } else {
872
                    return self.converter.convertTxs(data);
873
                }
874
            }), cb);
875
    } else {
876
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
877
    }
878
};
879
880
/**
881
 * get a paginated list of all webhooks associated with the api user
882
 *
883
 * @param [params]      object      pagination: {page: 1, limit: 20}
884
 * @param [cb]          function    callback function to call when request is complete
885
 * @return q.Promise
886
 */
887
APIClient.prototype.allWebhooks = function(params, cb) {
888
    var self = this;
889
890
    if (typeof params === "function") {
891
        cb = params;
892
        params = null;
893
    }
894
895
    return self.blocktrailClient.get("/webhooks", params, cb);
896
};
897
898
/**
899
 * create a new webhook
900
 *
901
 * @param url           string      the url to receive the webhook events
902
 * @param [identifier]  string      a unique identifier associated with the webhook
903
 * @param [cb]          function    callback function to call when request is complete
904
 * @return q.Promise
905
 */
906
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
907
    var self = this;
908
909
    if (typeof identifier === "function") {
910
        //mimic function overloading
911
        cb = identifier;
912
        identifier = null;
913
    }
914
915
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
916
};
917
918
/**
919
 * Converts a cash address to the legacy (base58) format
920
 * @param {string} input
921
 * @returns {string}
922
 */
923
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
924
    if (this.network === bitcoin.networks.bitcoincash ||
925
        this.network === bitcoin.networks.bitcoincashtestnet ||
926
        this.network === bitcoin.networks.bitcoincashregtest) {
927
        var address;
928
        try {
929
            bitcoin.address.fromBase58Check(input, this.network);
930
            return input;
931
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
932
933
        address = bitcoin.address.fromCashAddress(input, this.network);
934
        var prefix;
935
        if (address.version === bitcoin.script.types.P2PKH) {
936
            prefix = this.network.pubKeyHash;
937
        } else if (address.version === bitcoin.script.types.P2SH) {
938
            prefix = this.network.scriptHash;
939
        } else {
940
            throw new Error("Unsupported address type");
941
        }
942
943
        return bitcoin.address.toBase58Check(address.hash, prefix);
944
    }
945
946
    throw new Error("Cash addresses only work on bitcoin cash");
947
};
948
949
/**
950
 * Converts a legacy bitcoin to the new cashaddr format
951
 * @param {string} input
952
 * @returns {string}
953
 */
954
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
955
    if (this.network === bitcoin.networks.bitcoincash ||
956
        this.network === bitcoin.networks.bitcoincashtestnet ||
957
        this.network === bitcoin.networks.bitcoincashregtest
958
    ) {
959
        var address;
960
        try {
961
            bitcoin.address.fromCashAddress(input, this.network);
962
            return input;
963
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
964
965
        address = bitcoin.address.fromBase58Check(input, this.network);
966
        var scriptType;
967
        if (address.version === this.network.pubKeyHash) {
968
            scriptType = bitcoin.script.types.P2PKH;
969
        } else if (address.version === this.network.scriptHash) {
970
            scriptType = bitcoin.script.types.P2SH;
971
        } else {
972
            throw new Error("Unsupported address type");
973
        }
974
975
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
976
    }
977
978
    throw new Error("Cash addresses only work on bitcoin cash");
979
};
980
981
/**
982
 * get an existing webhook by it's identifier
983
 *
984
 * @param identifier    string      the unique identifier of the webhook to get
985
 * @param [cb]          function    callback function to call when request is complete
986
 * @return q.Promise
987
 */
988
APIClient.prototype.getWebhook = function(identifier, cb) {
989
    var self = this;
990
991
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
992
};
993
994
/**
995
 * update an existing webhook
996
 *
997
 * @param identifier    string      the unique identifier of the webhook
998
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
999
 * @param [cb]          function    callback function to call when request is complete
1000
 * @return q.Promise
1001
 */
1002
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
1003
    var self = this;
1004
1005
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
1006
};
1007
1008
/**
1009
 * deletes an existing webhook and any event subscriptions associated with it
1010
 *
1011
 * @param identifier    string      the unique identifier of the webhook
1012
 * @param [cb]          function    callback function to call when request is complete
1013
 * @return q.Promise
1014
 */
1015
APIClient.prototype.deleteWebhook = function(identifier, cb) {
1016
    var self = this;
1017
1018
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
1019
};
1020
1021
/**
1022
 * get a paginated list of all the events a webhook is subscribed to
1023
 *
1024
 * @param identifier    string      the unique identifier of the webhook
1025
 * @param [params]      object      pagination: {page: 1, limit: 20}
1026
 * @param [cb]          function    callback function to call when request is complete
1027
 * @return q.Promise
1028
 */
1029
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
1030
    var self = this;
1031
1032
    if (typeof params === "function") {
1033
        cb = params;
1034
        params = null;
1035
    }
1036
1037
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
1038
};
1039
1040
/**
1041
 * subscribes a webhook to transaction events for a particular transaction
1042
 *
1043
 * @param identifier    string      the unique identifier of the webhook
1044
 * @param transaction   string      the transaction hash
1045
 * @param confirmations integer     the amount of confirmations to send
1046
 * @param [cb]          function    callback function to call when request is complete
1047
 * @return q.Promise
1048
 */
1049
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
1050
    var self = this;
1051
    var postData = {
1052
        'event_type': 'transaction',
1053
        'transaction': transaction,
1054
        'confirmations': confirmations
1055
    };
1056
1057
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1058
};
1059
1060
/**
1061
 * subscribes a webhook to transaction events on a particular address
1062
 *
1063
 * @param identifier    string      the unique identifier of the webhook
1064
 * @param address       string      the address hash
1065
 * @param confirmations integer     the amount of confirmations to send
1066
 * @param [cb]          function    callback function to call when request is complete
1067
 * @return q.Promise
1068
 */
1069
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
1070
    var self = this;
1071
    var postData = {
1072
        'event_type': 'address-transactions',
1073
        'address': address,
1074
        'confirmations': confirmations
1075
    };
1076
1077
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1078
};
1079
1080
/**
1081
 * batch subscribes a webhook to multiple transaction events
1082
 *
1083
 * @param  identifier   string      the unique identifier of the webhook
1084
 * @param  batchData    array       An array of objects containing batch event data:
1085
 *                                  {address : 'address', confirmations : 'confirmations']
1086
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1087
 * @param [cb]          function    callback function to call when request is complete
1088
 * @return q.Promise
1089
 */
1090
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1091
    var self = this;
1092
    batchData.forEach(function(record) {
1093
        record.event_type = 'address-transactions';
1094
    });
1095
1096
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1097
};
1098
1099
/**
1100
 * subscribes a webhook to a new block event
1101
 *
1102
 * @param identifier    string      the unique identifier of the webhook
1103
 * @param [cb]          function    callback function to call when request is complete
1104
 * @return q.Promise
1105
 */
1106
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1107
    var self = this;
1108
    var postData = {
1109
        'event_type': 'block'
1110
    };
1111
1112
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1113
};
1114
1115
/**
1116
 * removes an address transaction event subscription from a webhook
1117
 *
1118
 * @param identifier    string      the unique identifier of the webhook
1119
 * @param address       string      the address hash
1120
 * @param [cb]          function    callback function to call when request is complete
1121
 * @return q.Promise
1122
 */
1123
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1124
    var self = this;
1125
1126
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1127
};
1128
1129
/**
1130
 * removes an transaction event subscription from a webhook
1131
 *
1132
 * @param identifier    string      the unique identifier of the webhook
1133
 * @param transaction   string      the transaction hash
1134
 * @param [cb]          function    callback function to call when request is complete
1135
 * @return q.Promise
1136
 */
1137
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1138
    var self = this;
1139
1140
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1141
};
1142
1143
/**
1144
 * removes a block event subscription from a webhook
1145
 *
1146
 * @param identifier    string      the unique identifier of the webhook
1147
 * @param [cb]          function    callback function to call when request is complete
1148
 * @return q.Promise
1149
 */
1150
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1151
    var self = this;
1152
1153
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1154
};
1155
1156
/**
1157
 * initialize an existing wallet
1158
 *
1159
 * Either takes two argument:
1160
 * @param options       object      {}
1161
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1162
 *
1163
 * Or takes three arguments (old, deprecated syntax):
1164
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1165
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1166
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1161. The second definition is ignored.
Loading history...
1167
 *
1168
 * @returns {q.Promise}
1169
 */
1170
APIClient.prototype.initWallet = function(options, cb) {
1171
    var self = this;
1172
1173
    if (typeof options !== "object") {
1174
        // get the old-style arguments
1175
        options = {
1176
            identifier: arguments[0],
1177
            passphrase: arguments[1]
1178
        };
1179
1180
        cb = arguments[2];
1181
    }
1182
1183
    if (options.check_backup_key) {
1184
        if (typeof options.check_backup_key !== "string") {
1185
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1186
        }
1187
    }
1188
1189
    var deferred = q.defer();
1190
    deferred.promise.spreadNodeify(cb);
1191
1192
    var identifier = options.identifier;
1193
1194
    if (!identifier) {
1195
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1196
        return deferred.promise;
1197
    }
1198
1199
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1200
        var keyIndex = options.keyIndex || result.key_index;
1201
1202
        options.walletVersion = result.wallet_version;
1203
1204
        if (options.check_backup_key) {
1205
            if (options.check_backup_key !== result.backup_public_key[0]) {
1206
                throw new Error("Backup key returned from server didn't match our own copy");
1207
            }
1208
        }
1209
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1210
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1211
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1212
        });
1213
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1214
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1215
        });
1216
1217
        // initialize wallet
1218
        var wallet = new Wallet(
1219
            self,
1220
            identifier,
1221
            options.walletVersion,
1222
            result.primary_mnemonic,
1223
            result.encrypted_primary_seed,
1224
            result.encrypted_secret,
1225
            primaryPublicKeys,
1226
            backupPublicKey,
1227
            blocktrailPublicKeys,
1228
            keyIndex,
1229
            result.segwit || 0,
1230
            self.testnet,
1231
            self.regtest,
1232
            result.checksum,
1233
            result.upgrade_key_index,
1234
            options.useCashAddress,
1235
            options.bypassNewAddressCheck
1236
        );
1237
1238
        wallet.recoverySecret = result.recovery_secret;
1239
1240
        if (!options.readOnly) {
1241
            return wallet.unlock(options).then(function() {
1242
                return wallet;
1243
            });
1244
        } else {
1245
            return wallet;
1246
        }
1247
    }));
1248
1249
    return deferred.promise;
1250
};
1251
1252
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1253
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1254
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1255
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1256
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1257
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1258
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1259
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1260
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1261
1262
/**
1263
 * create a new wallet
1264
 *   - will generate a new primary seed and backup seed
1265
 *
1266
 * Either takes two argument:
1267
 * @param options       object      {}
1268
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1269
 *
1270
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1271
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1267. The second definition is ignored.
Loading history...
1272
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1268. The second definition is ignored.
Loading history...
1273
 *
1274
 * Or takes four arguments (old, deprecated syntax):
1275
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1276
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1277
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1278
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1268. The second definition is ignored.
Loading history...
1279
 * @returns {q.Promise}
1280
 */
1281
APIClient.prototype.createNewWallet = function(options, cb) {
1282
    /* jshint -W071, -W074 */
1283
1284
    var self = this;
1285
1286
    if (typeof options !== "object") {
1287
        // get the old-style arguments
1288
        var identifier = arguments[0];
1289
        var passphrase = arguments[1];
1290
        var keyIndex = arguments[2];
1291
        cb = arguments[3];
1292
1293
        // keyIndex is optional
1294
        if (typeof keyIndex === "function") {
1295
            cb = keyIndex;
1296
            keyIndex = null;
1297
        }
1298
1299
        options = {
1300
            identifier: identifier,
1301
            passphrase: passphrase,
1302
            keyIndex: keyIndex
1303
        };
1304
    }
1305
1306
    // default to v3
1307
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1308
1309
    var deferred = q.defer();
1310
    deferred.promise.spreadNodeify(cb);
1311
1312
    q.nextTick(function() {
1313
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1314
1315
        options.keyIndex = options.keyIndex || 0;
1316
        options.passphrase = options.passphrase || options.password;
1317
        delete options.password;
1318
1319
        if (!options.identifier) {
1320
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1321
            return deferred.promise;
1322
        }
1323
1324
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1325
            self._createNewWalletV1(options)
1326
                .progress(function(p) { deferred.notify(p); })
1327
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1328
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1329
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1330
            self._createNewWalletV2(options)
1331
                .progress(function(p) { deferred.notify(p); })
1332
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1333
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1334
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1335
            self._createNewWalletV3(options)
1336
                .progress(function(p) { deferred.notify(p); })
1337
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1338
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1339
        } else {
1340
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1341
        }
1342
    });
1343
1344
    return deferred.promise;
1345
};
1346
1347
APIClient.prototype._createNewWalletV1 = function(options) {
1348
    var self = this;
1349
1350
    var deferred = q.defer();
1351
1352
    q.nextTick(function() {
1353
1354
        if (!options.primaryMnemonic && !options.primarySeed) {
1355
            if (!options.passphrase && !options.password) {
1356
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1357
                return deferred.promise;
1358
            } else {
1359
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1360
                if (options.storePrimaryMnemonic !== false) {
1361
                    options.storePrimaryMnemonic = true;
1362
                }
1363
            }
1364
        }
1365
1366
        if (!options.backupMnemonic && !options.backupPublicKey) {
1367
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1368
        }
1369
1370
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1371
1372
        self.resolvePrimaryPrivateKeyFromOptions(options)
1373
            .then(function(options) {
1374
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1375
1376
                return self.resolveBackupPublicKeyFromOptions(options)
1377
                    .then(function(options) {
1378
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1379
1380
                        // create a checksum of our private key which we'll later use to verify we used the right password
1381
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1382
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1383
                        var keyIndex = options.keyIndex;
1384
1385
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1386
1387
                        // send the public keys to the server to store them
1388
                        //  and the mnemonic, which is safe because it's useless without the password
1389
                        return self.storeNewWalletV1(
1390
                            options.identifier,
1391
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1392
                            [options.backupPublicKey.toBase58(), "M"],
1393
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1394
                            checksum,
1395
                            keyIndex,
1396
                            options.segwit || null
1397
                        )
1398
                            .then(function(result) {
1399
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1400
1401
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1402
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1403
                                });
1404
1405
                                var wallet = new Wallet(
1406
                                    self,
1407
                                    options.identifier,
1408
                                    Wallet.WALLET_VERSION_V1,
1409
                                    options.primaryMnemonic,
1410
                                    null,
1411
                                    null,
1412
                                    {keyIndex: primaryPublicKey},
1413
                                    options.backupPublicKey,
1414
                                    blocktrailPublicKeys,
1415
                                    keyIndex,
1416
                                    result.segwit || 0,
1417
                                    self.testnet,
1418
                                    self.regtest,
1419
                                    checksum,
1420
                                    result.upgrade_key_index,
1421
                                    options.useCashAddress,
1422
                                    options.bypassNewAddressCheck
1423
                                );
1424
1425
                                return wallet.unlock({
1426
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1427
                                    passphrase: options.passphrase,
1428
                                    primarySeed: options.primarySeed,
1429
                                    primaryMnemonic: null // explicit null
1430
                                }).then(function() {
1431
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1432
                                    return [
1433
                                        wallet,
1434
                                        {
1435
                                            walletVersion: wallet.walletVersion,
1436
                                            primaryMnemonic: options.primaryMnemonic,
1437
                                            backupMnemonic: options.backupMnemonic,
1438
                                            blocktrailPublicKeys: blocktrailPublicKeys
1439
                                        }
1440
                                    ];
1441
                                });
1442
                            });
1443
                    }
1444
                );
1445
            })
1446
            .then(
1447
            function(r) {
1448
                deferred.resolve(r);
1449
            },
1450
            function(e) {
1451
                deferred.reject(e);
1452
            }
1453
        )
1454
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1455
    });
1456
1457
    return deferred.promise;
1458
};
1459
1460 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1461
    var self = this;
1462
1463
    var deferred = q.defer();
1464
1465
    // avoid modifying passed options
1466
    options = _.merge({}, options);
1467
1468
    determineDataStorageV2_3(options)
1469
        .then(function(options) {
1470
            options.passphrase = options.passphrase || options.password;
1471
            delete options.password;
1472
1473
            // avoid deprecated options
1474
            if (options.primaryPrivateKey) {
1475
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1476
            }
1477
1478
            // seed should be provided or generated
1479
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1480
1481
            return options;
1482
        })
1483
        .then(function(options) {
1484
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1485
        })
1486
        .then(function(options) {
1487
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1488
        })
1489
        .then(function(options) {
1490
            // create a checksum of our private key which we'll later use to verify we used the right password
1491
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1492
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1493
            var keyIndex = options.keyIndex;
1494
1495
            // send the public keys and encrypted data to server
1496
            return self.storeNewWalletV2(
1497
                options.identifier,
1498
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1499
                [options.backupPublicKey.toBase58(), "M"],
1500
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1501
                options.storeDataOnServer ? options.encryptedSecret : false,
1502
                options.storeDataOnServer ? options.recoverySecret : false,
1503
                checksum,
1504
                keyIndex,
1505
                options.support_secret || null,
1506
                options.segwit || null
1507
            )
1508
                .then(
1509
                function(result) {
1510
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1511
1512
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1513
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1514
                    });
1515
1516
                    var wallet = new Wallet(
1517
                        self,
1518
                        options.identifier,
1519
                        Wallet.WALLET_VERSION_V2,
1520
                        null,
1521
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1522
                        options.storeDataOnServer ? options.encryptedSecret : null,
1523
                        {keyIndex: options.primaryPublicKey},
1524
                        options.backupPublicKey,
1525
                        blocktrailPublicKeys,
1526
                        keyIndex,
1527
                        result.segwit || 0,
1528
                        self.testnet,
1529
                        self.regtest,
1530
                        checksum,
1531
                        result.upgrade_key_index,
1532
                        options.useCashAddress,
1533
                        options.bypassNewAddressCheck
1534
                    );
1535
1536
                    // pass along decrypted data to avoid extra work
1537
                    return wallet.unlock({
1538
                        walletVersion: Wallet.WALLET_VERSION_V2,
1539
                        passphrase: options.passphrase,
1540
                        primarySeed: options.primarySeed,
1541
                        secret: options.secret
1542
                    }).then(function() {
1543
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1544
                        return [
1545
                            wallet,
1546
                            {
1547
                                walletVersion: wallet.walletVersion,
1548
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1549
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1550
                                    null,
1551
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1552
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1553
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1554
                                    null,
1555
                                encryptedSecret: options.encryptedSecret ?
1556
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1557
                                    null,
1558
                                blocktrailPublicKeys: blocktrailPublicKeys
1559
                            }
1560
                        ];
1561
                    });
1562
                }
1563
            );
1564
        })
1565
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1566
1567
    return deferred.promise;
1568
};
1569
1570 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1571
    var self = this;
1572
1573
    var deferred = q.defer();
1574
1575
    // avoid modifying passed options
1576
    options = _.merge({}, options);
1577
1578
    determineDataStorageV2_3(options)
1579
        .then(function(options) {
1580
            options.passphrase = options.passphrase || options.password;
1581
            delete options.password;
1582
1583
            // avoid deprecated options
1584
            if (options.primaryPrivateKey) {
1585
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1586
            }
1587
1588
            // seed should be provided or generated
1589
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1590
1591
            return options;
1592
        })
1593
        .then(function(options) {
1594
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1595
        })
1596
        .then(function(options) {
1597
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1598
        })
1599
        .then(function(options) {
1600
            // create a checksum of our private key which we'll later use to verify we used the right password
1601
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1602
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1603
            var keyIndex = options.keyIndex;
1604
1605
            // send the public keys and encrypted data to server
1606
            return self.storeNewWalletV3(
1607
                options.identifier,
1608
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1609
                [options.backupPublicKey.toBase58(), "M"],
1610
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1611
                options.storeDataOnServer ? options.encryptedSecret : false,
1612
                options.storeDataOnServer ? options.recoverySecret : false,
1613
                checksum,
1614
                keyIndex,
1615
                options.support_secret || null,
1616
                options.segwit || null
1617
            )
1618
                .then(
1619
                    // result, deferred, self(apiclient)
1620
                    function(result) {
1621
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1622
1623
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1624
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1625
                        });
1626
1627
                        var wallet = new Wallet(
1628
                            self,
1629
                            options.identifier,
1630
                            Wallet.WALLET_VERSION_V3,
1631
                            null,
1632
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1633
                            options.storeDataOnServer ? options.encryptedSecret : null,
1634
                            {keyIndex: options.primaryPublicKey},
1635
                            options.backupPublicKey,
1636
                            blocktrailPublicKeys,
1637
                            keyIndex,
1638
                            result.segwit || 0,
1639
                            self.testnet,
1640
                            self.regtest,
1641
                            checksum,
1642
                            result.upgrade_key_index,
1643
                            options.useCashAddress,
1644
                            options.bypassNewAddressCheck
1645
                        );
1646
1647
                        // pass along decrypted data to avoid extra work
1648
                        return wallet.unlock({
1649
                            walletVersion: Wallet.WALLET_VERSION_V3,
1650
                            passphrase: options.passphrase,
1651
                            primarySeed: options.primarySeed,
1652
                            secret: options.secret
1653
                        }).then(function() {
1654
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1655
                            return [
1656
                                wallet,
1657
                                {
1658
                                    walletVersion: wallet.walletVersion,
1659
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1660
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1661
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1662
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1663
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1664
                                    blocktrailPublicKeys: blocktrailPublicKeys
1665
                                }
1666
                            ];
1667
                        });
1668
                    }
1669
                );
1670
        })
1671
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1672
1673
    return deferred.promise;
1674
};
1675
1676
function verifyPublicBip32Key(bip32Key, network) {
1677
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1678
    if (typeof hk.keyPair.d !== "undefined") {
1679
        throw new Error('BIP32Key contained private key material - abort');
1680
    }
1681
1682
    if (bip32Key[1].slice(0, 1) !== "M") {
1683
        throw new Error("BIP32Key contained non-public path - abort");
1684
    }
1685
}
1686
1687
function verifyPublicOnly(walletData, network) {
1688
    verifyPublicBip32Key(walletData.primary_public_key, network);
1689
    verifyPublicBip32Key(walletData.backup_public_key, network);
1690
}
1691
1692
/**
1693
 * create wallet using the API
1694
 *
1695
 * @param identifier            string      the wallet identifier to create
1696
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1697
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1698
 * @param primaryMnemonic       string      mnemonic to store
1699
 * @param checksum              string      checksum to store
1700
 * @param keyIndex              int         keyIndex that was used to create wallet
1701
 * @param segwit                bool
1702
 * @returns {q.Promise}
1703
 */
1704
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1705
                                                checksum, keyIndex, segwit) {
1706
    var self = this;
1707
1708
    var postData = {
1709
        identifier: identifier,
1710
        wallet_version: Wallet.WALLET_VERSION_V1,
1711
        primary_public_key: primaryPublicKey,
1712
        backup_public_key: backupPublicKey,
1713
        primary_mnemonic: primaryMnemonic,
1714
        checksum: checksum,
1715
        key_index: keyIndex,
1716
        segwit: segwit
1717
    };
1718
1719
    verifyPublicOnly(postData, self.network);
1720
1721
    return self.blocktrailClient.post("/wallet", null, postData);
1722
};
1723
1724
/**
1725
 * create wallet using the API
1726
 *
1727
 * @param identifier            string      the wallet identifier to create
1728
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1729
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1730
 * @param encryptedPrimarySeed  string      openssl format
1731
 * @param encryptedSecret       string      openssl format
1732
 * @param recoverySecret        string      openssl format
1733
 * @param checksum              string      checksum to store
1734
 * @param keyIndex              int         keyIndex that was used to create wallet
1735
 * @param supportSecret         string
1736
 * @param segwit                bool
1737
 * @returns {q.Promise}
1738
 */
1739
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1740
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1741
    var self = this;
1742
1743
    var postData = {
1744
        identifier: identifier,
1745
        wallet_version: Wallet.WALLET_VERSION_V2,
1746
        primary_public_key: primaryPublicKey,
1747
        backup_public_key: backupPublicKey,
1748
        encrypted_primary_seed: encryptedPrimarySeed,
1749
        encrypted_secret: encryptedSecret,
1750
        recovery_secret: recoverySecret,
1751
        checksum: checksum,
1752
        key_index: keyIndex,
1753
        support_secret: supportSecret || null,
1754
        segwit: segwit
1755
    };
1756
1757
    verifyPublicOnly(postData, self.network);
1758
1759
    return self.blocktrailClient.post("/wallet", null, postData);
1760
};
1761
1762
/**
1763
 * create wallet using the API
1764
 *
1765
 * @param identifier            string      the wallet identifier to create
1766
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1767
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1768
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1769
 * @param encryptedSecret       Buffer      buffer of ciphertext
1770
 * @param recoverySecret        Buffer      buffer of recovery secret
1771
 * @param checksum              string      checksum to store
1772
 * @param keyIndex              int         keyIndex that was used to create wallet
1773
 * @param supportSecret         string
1774
 * @param segwit                bool
1775
 * @returns {q.Promise}
1776
 */
1777
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1778
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1779
    var self = this;
1780
1781
    var postData = {
1782
        identifier: identifier,
1783
        wallet_version: Wallet.WALLET_VERSION_V3,
1784
        primary_public_key: primaryPublicKey,
1785
        backup_public_key: backupPublicKey,
1786
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1787
        encrypted_secret: encryptedSecret.toString('base64'),
1788
        recovery_secret: recoverySecret.toString('hex'),
1789
        checksum: checksum,
1790
        key_index: keyIndex,
1791
        support_secret: supportSecret || null,
1792
        segwit: segwit
1793
    };
1794
1795
    verifyPublicOnly(postData, self.network);
1796
1797
    return self.blocktrailClient.post("/wallet", null, postData);
1798
};
1799
1800
/**
1801
 * create wallet using the API
1802
 *
1803
 * @param identifier            string      the wallet identifier to create
1804
 * @param postData              object
1805
 * @param [cb]                  function    callback(err, result)
1806
 * @returns {q.Promise}
1807
 */
1808
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1809
    var self = this;
1810
1811
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1812
};
1813
1814
/**
1815
 * upgrade wallet to use a new account number
1816
 *  the account number specifies which blocktrail cosigning key is used
1817
 *
1818
 * @param identifier            string      the wallet identifier
1819
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1820
 * @param keyIndex              int         keyIndex that was used to create wallet
1821
 * @param [cb]                  function    callback(err, result)
1822
 * @returns {q.Promise}
1823
 */
1824
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1825
    var self = this;
1826
1827
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1828
        key_index: keyIndex,
1829
        primary_public_key: primaryPublicKey
1830
    }, cb);
1831
};
1832
1833
/**
1834
 * get the balance for the wallet
1835
 *
1836
 * @param identifier            string      the wallet identifier
1837
 * @param [cb]                  function    callback(err, result)
1838
 * @returns {q.Promise}
1839
 */
1840
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1841
    var self = this;
1842
1843
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1844
};
1845
1846
/**
1847
 * do HD wallet discovery for the wallet
1848
 *
1849
 * @param identifier            string      the wallet identifier
1850
 * @param [cb]                  function    callback(err, result)
1851
 * @returns {q.Promise}
1852
 */
1853
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1854
    var self = this;
1855
1856
    return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1857
};
1858
1859
1860
/**
1861
 * get a new derivation number for specified parent path
1862
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1863
 *
1864
 * @param identifier            string      the wallet identifier
1865
 * @param path                  string      the parent path for which to get a new derivation,
1866
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1867
 * @param [cb]                  function    callback(err, result)
1868
 * @returns {q.Promise}
1869
 */
1870
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1871
    var self = this;
1872
1873
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1874
};
1875
1876
1877
/**
1878
 * delete the wallet
1879
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1880
 *  is required to be able to delete a wallet
1881
 *
1882
 * @param identifier            string      the wallet identifier
1883
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1884
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1885
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1886
 * @param [cb]                  function    callback(err, result)
1887
 * @returns {q.Promise}
1888
 */
1889
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1890
    var self = this;
1891
1892
    if (typeof force === "function") {
1893
        cb = force;
1894
        force = false;
1895
    }
1896
1897
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1898
        checksum: checksumAddress,
1899
        signature: checksumSignature
1900
    }, cb);
1901
};
1902
1903
/**
1904
 * use the API to get the best inputs to use based on the outputs
1905
 *
1906
 * the return array has the following format:
1907
 * [
1908
 *  "utxos" => [
1909
 *      [
1910
 *          "hash" => "<txHash>",
1911
 *          "idx" => "<index of the output of that <txHash>",
1912
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1913
 *          "value" => 32746327,
1914
 *          "address" => "1address",
1915
 *          "path" => "m/44'/1'/0'/0/13",
1916
 *          "redeem_script" => "<redeemScript-hex>",
1917
 *      ],
1918
 *  ],
1919
 *  "fee"   => 10000,
1920
 *  "change"=> 1010109201,
1921
 * ]
1922
 *
1923
 * @param identifier        string      the wallet identifier
1924
 * @param pay               array       {'address': (int)value}     coins to send
1925
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1926
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1927
 * @param feeStrategy       string      defaults to
1928
 * @param options
1929
 * @param [cb]              function    callback(err, utxos, fee, change)
1930
 * @returns {q.Promise}
1931
 */
1932
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1933
    var self = this;
1934
1935
    if (typeof feeStrategy === "function") {
1936
        cb = feeStrategy;
1937
        feeStrategy = null;
1938
        options = {};
1939
    } else if (typeof options === "function") {
1940
        cb = options;
1941
        options = {};
1942
    }
1943
1944
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1945
    options = options || {};
1946
1947
    var deferred = q.defer();
1948
    deferred.promise.spreadNodeify(cb);
1949
1950
    var params = {
1951
        lock: lockUTXO,
1952
        zeroconf: allowZeroConf ? 1 : 0,
1953
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1954
        fee_strategy: feeStrategy
1955
    };
1956
1957
    if (options.forcefee) {
1958
        params['forcefee'] = options.forcefee;
1959
    }
1960
1961
    deferred.resolve(
1962
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1963
            function(result) {
1964
                return [result.utxos, result.fee, result.change, result];
1965
            },
1966
            function(err) {
1967
                if (err.message.match(/too low to pay the fee/)) {
1968
                    throw blocktrail.WalletFeeError(err);
1969
                }
1970
1971
                throw err;
1972
            }
1973
        )
1974
    );
1975
1976
    return deferred.promise;
1977
};
1978
1979
/**
1980
 * @param [cb]              function    callback(err, utxos, fee, change)
1981
 * @returns {q.Promise}
1982
 */
1983
APIClient.prototype.feePerKB = function(cb) {
1984
    var self = this;
1985
1986
    var deferred = q.defer();
1987
    deferred.promise.spreadNodeify(cb);
1988
1989
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
1990
1991
    return deferred.promise;
1992
};
1993
1994
/**
1995
 * send the transaction using the API
1996
 *
1997
 * @param identifier        string      the wallet identifier
1998
 * @param txHex             string      partially signed transaction as hex string
1999
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
2000
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
2001
 * @param [twoFactorToken]  string      2FA token
2002
 * @param [prioboost]       bool
2003
 * @param [cb]              function    callback(err, txHash)
2004
 * @returns {q.Promise}
2005
 */
2006
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
2007
    var self = this;
2008
2009
    if (typeof twoFactorToken === "function") {
2010
        cb = twoFactorToken;
2011
        twoFactorToken = null;
2012
        prioboost = false;
2013
    } else if (typeof prioboost === "function") {
2014
        cb = prioboost;
2015
        prioboost = false;
2016
    }
2017
2018
    var data = {
2019
        paths: paths,
2020
        two_factor_token: twoFactorToken
2021
    };
2022
    if (typeof txHex === "string") {
2023
        data.raw_transaction = txHex;
2024
    } else if (typeof txHex === "object") {
2025
        Object.keys(txHex).map(function(key) {
2026
            data[key] = txHex[key];
2027
        });
2028
    }
2029
2030
    return self.blocktrailClient.post(
2031
        "/wallet/" + identifier + "/send",
2032
        {
2033
            check_fee: checkFee ? 1 : 0,
2034
            prioboost: prioboost ? 1 : 0
2035
        },
2036
        data,
2037
        cb
2038
    );
2039
};
2040
2041
/**
2042
 * setup a webhook for this wallet
2043
 *
2044
 * @param identifier        string      the wallet identifier
2045
 * @param webhookIdentifier string      identifier for the webhook
2046
 * @param url               string      URL to receive webhook events
2047
 * @param [cb]              function    callback(err, webhook)
2048
 * @returns {q.Promise}
2049
 */
2050
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
2051
    var self = this;
2052
2053
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
2054
};
2055
2056
/**
2057
 * delete a webhook that was created for this wallet
2058
 *
2059
 * @param identifier        string      the wallet identifier
2060
 * @param webhookIdentifier string      identifier for the webhook
2061
 * @param [cb]              function    callback(err, success)
2062
 * @returns {q.Promise}
2063
 */
2064
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
2065
    var self = this;
2066
2067
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
2068
};
2069
2070
/**
2071
 * get all transactions for an wallet (paginated)
2072
 *
2073
 * @param identifier    string      wallet identifier
2074
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2075
 * @param [cb]          function    callback function to call when request is complete
2076
 * @return q.Promise
2077
 */
2078
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
2079
    var self = this;
2080
2081
    if (typeof params === "function") {
2082
        cb = params;
2083
        params = null;
2084
    }
2085
2086
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2087
};
2088
2089
/**
2090
 * get all addresses for an wallet (paginated)
2091
 *
2092
 * @param identifier    string      wallet identifier
2093
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2094
 * @param [cb]          function    callback function to call when request is complete
2095
 * @return q.Promise
2096
 */
2097
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2098
    var self = this;
2099
2100
    if (typeof params === "function") {
2101
        cb = params;
2102
        params = null;
2103
    }
2104
2105
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2106
};
2107
2108
/**
2109
 * @param identifier    string      wallet identifier
2110
 * @param address       string      the address to label
2111
 * @param label         string      the label
2112
 * @param [cb]          function    callback(err, res)
2113
 * @return q.Promise
2114
 */
2115
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2116
    var self = this;
2117
2118
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2119
};
2120
2121
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2122
    var self = this;
2123
2124
    if (typeof feeStrategy === "function") {
2125
        cb = feeStrategy;
2126
        feeStrategy = null;
2127
    } else if (typeof options === "function") {
2128
        cb = options;
2129
        options = {};
2130
    }
2131
2132
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2133
    options = options || {};
2134
2135
    var params = {
2136
        outputs: options.outputs ? options.outputs : 1,
2137
        zeroconf: allowZeroConf ? 1 : 0,
2138
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2139
        fee_strategy: feeStrategy
2140
    };
2141
2142
    if (options.forcefee) {
2143
        params['forcefee'] = options.forcefee;
2144
    }
2145
2146
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2147
};
2148
2149
/**
2150
 * get all UTXOs for an wallet (paginated)
2151
 *
2152
 * @param identifier    string      wallet identifier
2153
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2154
 * @param [cb]          function    callback function to call when request is complete
2155
 * @return q.Promise
2156
 */
2157
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2158
    var self = this;
2159
2160
    if (typeof params === "function") {
2161
        cb = params;
2162
        params = null;
2163
    }
2164
2165
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2166
};
2167
2168
/**
2169
 * get a paginated list of all wallets associated with the api user
2170
 *
2171
 * @param [params]      object      pagination: {page: 1, limit: 20}
2172
 * @param [cb]          function    callback function to call when request is complete
2173
 * @return q.Promise
2174
 */
2175
APIClient.prototype.allWallets = function(params, cb) {
2176
    var self = this;
2177
2178
    if (typeof params === "function") {
2179
        cb = params;
2180
        params = null;
2181
    }
2182
2183
    return self.blocktrailClient.get("/wallets", params, true, cb);
2184
};
2185
2186
/**
2187
 * verify a message signed bitcoin-core style
2188
 *
2189
 * @param message        string
2190
 * @param address        string
2191
 * @param signature      string
2192
 * @param [cb]          function    callback function to call when request is complete
2193
 * @return q.Promise
2194
 */
2195
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2196
    var self = this;
2197
2198
    var deferred = q.defer();
2199
    deferred.promise.nodeify(cb);
2200
    try {
2201
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2202
        deferred.resolve(result);
2203
    } catch (e) {
2204
        deferred.reject(e);
2205
    }
2206
2207
    return deferred.promise;
2208
};
2209
2210
/**
2211
 * max is 0.001
2212
 * testnet only
2213
 *
2214
 * @param address
2215
 * @param amount
2216
 * @param cb
2217
 */
2218
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2219
    var self = this;
2220
2221
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2222
};
2223
2224
/**
2225
 * send a raw transaction
2226
 *
2227
 * @param rawTransaction    string      raw transaction as HEX
2228
 * @param [cb]              function    callback function to call when request is complete
2229
 * @return q.Promise
2230
 */
2231
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2232
    var self = this;
2233
2234
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2235
};
2236
2237
/**
2238
 * get the current price index
2239
 *
2240
 * @param [cb]          function    callback({'USD': 287.30})
2241
 * @return q.Promise
2242
 */
2243
APIClient.prototype.price = function(cb) {
2244
    var self = this;
2245
2246
    return self.blocktrailClient.get("/price", null, false, cb);
2247
};
2248
2249
module.exports = APIClient;
2250